package com.examples.gateway.helper.filter;

import cn.hutool.core.util.StrUtil;
import com.examples.core.req.ExamHeader;
import com.examples.core.tool.utils.IpUtils;
import com.examples.gateway.helper.Constants;
import com.examples.gateway.listener.OperateLogEvent;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;

/**
 * @ClassName: LogGlobalFilter
 * @Description: TODO
 * @Author: DAIYALU
 * @Date: 2024/5/14
 * @Version: 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LogGlobalFilter extends AbstractGlobalHelperFilter {

    private final ApplicationContext context;
    private final static Joiner JOINER = Joiner.on("");

    @Override
    public Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator response = new ServerHttpResponseDecorator(originalResponse) {
            @NotNull
            @Override
            public Mono<Void> writeWith(@NotNull Publisher<? extends DataBuffer> body) {
                HttpStatus status = getStatusCode();
                // 添加日志
                addLog(exchange,status);
                return super.writeWith(body);
            }

            @NotNull
            @Override
            public Mono<Void> writeAndFlushWith(@NotNull Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p -> p));
            }
        };
        return chain.filter(exchange.mutate().response(response).build());
    }

    private void addLog(ServerWebExchange exchange,HttpStatus status){
        try{
            ServerHttpRequest request = exchange.getRequest();
            String menuCode = request.getHeaders().getFirst(Constants.REQUEST_HEADER_MENU_CODE);
            ExamHeader header = exchange.getAttribute(Constants.EXAMPLES_GATEWAY_USER_DETAIL);
            LocalDateTime reqTime = exchange.getAttribute(Constants.EXAMPLES_GATEWAY_REQ_TIME);
            String path = request.getURI().getPath();
            String serverName = (String) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);
            log.info("serverName:{}",serverName);
            if(StrUtil.isBlank(menuCode)){
                return;
            }
            if(null == header){
                return;
            }
            if(null == reqTime){
                return;
            }
            if(null == status){
                return;
            }
            OperateLogEvent event = new OperateLogEvent(this);
            event.setUserId(header.getUserId());
            event.setUserType(header.getIdentity());
            event.setMenuCode(menuCode);
            event.setServiceName(serverName);
            event.setRequestMethod(request.getMethodValue());
            event.setRequestUrl(path);
            event.setUserIp(IpUtils.getRealIpAddress(request));
            event.setStartTime(reqTime);
            event.setDuration(Duration.between(reqTime,LocalDateTime.now()).toMillis());
            event.setResultCode(status.value());
            context.publishEvent(event);
        }catch (Exception e){
            log.error("记录操作日志异常，失败原因：{}", Throwables.getStackTraceAsString(e));
        }
    }

    @Override
    public int getOrder() {
        return -10;
    }
}
